package Derivation;


import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import Basics.*;

public class DeriveByProgramBottomUp extends DeriveByProgram 
{	
	
	Map<Atom, HashSet<Body>> provenanceProgram = new HashMap<Atom, HashSet<Body>>();
	
	Set<Atom> addedInstAtomsInLastIteration = new HashSet<Atom>();
	
	Set<Atom> atomsAddedSoFar = new HashSet<Atom>();
	
	int level = 0;
	
	int beginIdx = 0;
	
	public DeriveByProgramBottomUp (Rule ... irs)
	{
		super(irs);
	}
	
	
	
	public DeriveByProgramBottomUp (Program p)
	{
		super(p);
	}
	

	public Set<Atom> getAtomsAddedSoFar() 
	{
		return atomsAddedSoFar;
	}

	
	public Set<Atom> getAddedInstAtomsInLastIteration() 
	{
		return addedInstAtomsInLastIteration;
	}
	
	
	

	public Map<Atom, HashSet<Body>> getProvenanceProgram() 
	{
		return provenanceProgram;
	}



	/*************************************************************************************************************/
	/** Title: DeriveFromProgramIterationBottomUp																				
	/** Description: Finds all Derivations for the rule according to db 				
	/*************************************************************************************************************/
	
	public void DeriveFromProgramIterationBottomUp (boolean forIntersection)
	{
		level++;
		Reset();
		this.provenanceProgram.clear();
		//long startTime = System.currentTimeMillis();
		
		for (int i = beginIdx; i < this.p.getRules().size(); i++)
		{
			Rule rule = this.p.getRules().elementAt(i);
			if (false == forIntersection) 
			{
				if (false == IsBodyEDB(rule) || true == this.addedInstAtomsInLastIteration.isEmpty()) //if body of rule relies on derived facts.
				{
					InstantiateAllRelevantsInRule(rule, forIntersection);
				}
			}
			
			else if (rule.getDerivedInlevel() == level)
			{
				InstantiateAllRelevantsInRule(rule, forIntersection);
			}
			
			else
			{
				beginIdx = i;
				break;
			}
		}
		long end = System.currentTimeMillis();
		/*if ((end-startTime) > 1000) 
		{
			System.out.println("DeriveFromProgramIterationBottomUp:: time for iteration over all rules: " + (end-startTime));
			//System.out.println("DeriveFromProgramIterationBottomUp:: numRulesUsed = " + numRulesUsed);
		}*/

		this.addedInstAtomsInLastIteration.clear();
		UpdateAtomsAdded(false);
		UpdateAtomsAdded(true);
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: IsBodyEDB																				
	/** Description: Check if rule can derive a fact only with DB atoms  				
	/*************************************************************************************************************/
	
	public boolean IsBodyEDB (Rule rule)
	{
		boolean retVal = true;
		for (Atom bodyAtom : rule.getBody().getAtoms()) 
		{
			if (false == bodyAtom.IsAtomRelationEdb(this.p)) 
			{
				retVal = false;
				break;
			}
		}
		
		return retVal;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: UpdateAddedInstAtoms																				
	/** Description:  				
	/*************************************************************************************************************/

	public void UpdateAtomsAdded (boolean isAtomsAddedSoFar)
	{
		//long startTime = System.currentTimeMillis();
		for (Atom atom : this.provenanceProgram.keySet()) 
		{
			if (true == isAtomsAddedSoFar) 
			{
				this.atomsAddedSoFar.add(atom);
			}
			
			else
			{
				this.addedInstAtomsInLastIteration.add(atom);
			}
		}
		/*long endTime = System.currentTimeMillis();
		if ((endTime-startTime) > 1000) {
			System.out.println("iteration time: "  + (endTime-startTime));
			System.out.println("size of atomsAddedSoFar after adding: " + atomsAddedSoFar.size());
		}*/
	}
	
	
	/*************************************************************************************************************/
	/** Title: InstantiateAllRelevantsInRule																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public void InstantiateAllRelevantsInRule (Rule rule, boolean forIntersection)
	{
		List<Rule> instRuleVector = new CopyOnWriteArrayList<Rule>();
		
		//long startTime = System.currentTimeMillis();
		if (false == forIntersection) 
		{
			for (Atom bodyAtom : rule.getBody().getAtoms()) 
			{
				for (Atom instAtom : this.addedInstAtomsInLastIteration) 
				{
					if (instAtom.getName().equals(bodyAtom.getName()))
					{
						Atom copyOfinstAtom = new Atom (instAtom);
						Rule temp = new Rule (rule);
						temp.SwapToInstAtomAndPartiallyInst(bodyAtom, copyOfinstAtom);
						if (temp.isFullyInst())
						{
							AddToProv(temp, level);
							temp.getHead().setRuleUsed(rule);
						}

						else if (false == instRuleVector.contains(temp))
						{
							instRuleVector.add(temp);
						}
					}
				}
			}
		}
		
		else
		{
			AddToProv(rule, level);
			rule.getBody().setRuleUsed(rule);//added for the top-k
			//rule.setUsedAlready(true);
		}
		
		if ((false == instRuleVector.isEmpty() || true == this.addedInstAtomsInLastIteration.isEmpty()) && false == forIntersection)//) && false == forIntersection
		{
			DeriveByRuleBottomUp ruleDeriver = new DeriveByRuleBottomUp(rule, this.p, this.provenanceProgram);
			ruleDeriver.FindDerivationsForRuleBottomUp(instRuleVector, forIntersection, level);	
		}
		
		/*long endTime = System.currentTimeMillis();
		if ((endTime-startTime) > 200)
		{
			System.out.println("DeriveByProgramBottomUp::InstantiateAllRelevantsInRule:: iteration time: "  + (endTime-startTime));
			System.out.println("DeriveByProgramBottomUp::InstantiateAllRelevantsInRule:: rule derived: "  + rule);
		}*/
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: AddToProv																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public void AddToProv (Rule rule, int level)
	{
		if (false == this.provenanceProgram.containsKey(rule.getHead()))
		{
			this.provenanceProgram.put(rule.getHead(), new HashSet<Body>());
		}
		
		rule.getBody().setDerivedInlevel(level);
		rule.getBody().setRuleUsed(rule);//added for the top-k
		this.provenanceProgram.get(rule.getHead()).add(rule.getBody());
	}	
	
	
	

	/*************************************************************************************************************/
	/** Title: Reset																				
	/** Description: resets the parameters for next step of the program			
	/*************************************************************************************************************/
	
	public void Reset ()
	{
		//this.addedInstAtomsInLastIteration.clear();
	}
}
